﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Management;

namespace HybridLab.Virtual.HyperV
{
	internal enum VMState : ushort
	{
		On = 2,
		Off = 3,
		Saved = 6
	}

	internal static class HyperVUtils
	{
		public const string VirtualMachineClass = "Msvm_ComputerSystem";
		public const string SettingsData = "Msvm_VirtualSystemsettingData";
		public const string ExchangeComponent = "Msvm_KvpExchangeComponent";

		public const string CreateSnapshot = "CreateSnapshot";
		public const string ApplySnapshot = "ApplySnapshot";
		public const string ApplySnapshot1 = "ApplyVirtualSystemSnapshot";
		public const string ModifySystemSettings = "ModifySystemSettings";
		public const string RequestStateChange = "RequestStateChange";

		public const string RealizedSnapshot = "Microsoft:Hyper-V:Snapshot:Realized";
		public const uint FullSnapshot = 2;

		internal static ManagementObject GetHyperVManagementService(ManagementScope scope)
		{
			try
			{
				return GetWmiObject(scope, "Msvm_VirtualSystemManagementService", null);
			}
			catch (Exception ex)
			{
				throw new Exception(string.Format("Cannot connect to Virtual System Management Service. Are you logging in as adminstrator? {0}", ex.Message),
					ex);
			}
		}

		public static ManagementObject GetVirtualMachineSnapshotService(ManagementScope scope)
		{
			return GetWmiObject(scope, "Msvm_VirtualSystemSnapshotService", null);
		}

		internal static ManagementObject GetWmiObject(ManagementScope scope, string classname, string where = null)
		{
			var resultset = GetWmiObjects(scope, classname, where);
			if (resultset.Count != 1)
			{
				throw new InvalidOperationException(string.Format("Cannot locate {0} where {1}", classname, where ?? "(no filter)"));
			}

			var result = resultset.FirstOrDefault();
			if (result == null)
			{
				throw new InvalidOperationException("Failure retrieving " + classname + " where " + where ?? "(no filter)");
			}
			return result;
		}

		internal static List<ManagementObject> GetWmiObjects(ManagementScope scope, string classname, string where = null)
		{
			string query;
			if (where != null)
			{
				query = string.Format("select * from {0} where {1}", classname, where);
			}
			else
			{
				query = string.Format(CultureInfo.InvariantCulture, "select * from {0}", classname);
			}

			ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, new ObjectQuery(query));
			return searcher.Get().Cast<ManagementObject>().ToList();
		}

		internal static InvokeResult InvokeMethod(ManagementObject hyperVService, string methodName, ManagementBaseObject inParams, bool waitForCompleted)
		{
			var result = InvokeMethod(hyperVService, methodName, inParams);

			if (!waitForCompleted)
			{
				return result;
			}
			else // wait for ReturnCode.Completed
			{
				if (result.ReturnCode == ReturnCode.Completed)
				{
					return result;
				}

				if (result.Job == null)
				{
					throw new HyperVException("ReturnCode != Completed but also no Job returned! ReturnCode is " + result.ReturnCode.ToString());
				}
				else
				{
					result.Job.WaitOn(JobState.Starting, JobState.Running);

					if (result.Job.State == JobState.Completed)
					{
						return result;
					}

					if (result.Job.State == JobState.Exception)
					{
						throw new HyperVException(result.Job);
					}

					string exceptionMessage = "Job State did not transition to 'Completed' from 'Starting' or 'Running'. Encountered state: " + result.Job.State.ToString();
					throw new HyperVException(result.Job);
				}
			}
		}

		internal static InvokeResult InvokeMethod(ManagementObject hyperVService, string methodName, ManagementBaseObject inParams)
		{
			var invokeResult = new InvokeResult();

			var moResult = hyperVService.InvokeMethod(methodName, inParams, new InvokeMethodOptions() { Timeout = TimeSpan.FromSeconds(30) });

			if (moResult.Properties.OfType<PropertyData>().Select(pd => pd.Name).Contains("Job") &&	moResult.Properties["Job"].Value != null)
			{
				invokeResult.Job = new Job(new ManagementObject(moResult.Properties["Job"].Value.ToString()));
			}

			invokeResult.ReturnCode = (ReturnCode)moResult.Properties["ReturnValue"].Value;

			if (moResult.Properties.Count > 0)
			{
				invokeResult.OutParameters = moResult.Properties;
			}

			return invokeResult;
		}
	}
}
